🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / settings / src / main / kotlin / org / meshtastic / feature / settings / SettingsViewModel.kt
Displaying Raw • Download
feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt 1c3784235e7e20ed88e054097d2e7addbff92dc3 (1c378423) Text, 13.68 KB
T8b949e/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.settings
Tff7b72import T7ee787android.app.Application
Tff7b72import T7ee787android.net.Uri
Tff7b72import T7ee787androidx.lifecycle.ViewModel
Tff7b72import T7ee787androidx.lifecycle.viewModelScope
Tff7b72import T7ee787dagger.hilt.android.lifecycle.HiltViewModel
Tff7b72import T7ee787kotlinx.coroutines.Dispatchers
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.asStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.combine
Tff7b72import T7ee787kotlinx.coroutines.flow.first
Tff7b72import T7ee787kotlinx.coroutines.flow.flatMapLatest
Tff7b72import T7ee787kotlinx.coroutines.flow.flow
Tff7b72import T7ee787kotlinx.coroutines.flow.flowOf
Tff7b72import T7ee787kotlinx.coroutines.flow.map
Tff7b72import T7ee787kotlinx.coroutines.flow.update
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787kotlinx.coroutines.withContext
Tff7b72import T7ee787org.meshtastic.core.common.BuildConfigProvider
Tff7b72import T7ee787org.meshtastic.core.data.repository.DeviceHardwareRepository
Tff7b72import T7ee787org.meshtastic.core.data.repository.MeshLogRepository
Tff7b72import T7ee787org.meshtastic.core.data.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.data.repository.RadioConfigRepository
Tff7b72import T7ee787org.meshtastic.core.database.DatabaseConstants
Tff7b72import T7ee787org.meshtastic.core.database.DatabaseManager
Tff7b72import T7ee787org.meshtastic.core.database.entity.MyNodeEntity
Tff7b72import T7ee787org.meshtastic.core.database.model.Node
Tff7b72import T7ee787org.meshtastic.core.datastore.UiPreferencesDataSource
Tff7b72import T7ee787org.meshtastic.core.model.Position
Tff7b72import T7ee787org.meshtastic.core.model.util.positionToMeter
Tff7b72import T7ee787org.meshtastic.core.prefs.radio.RadioPrefs
Tff7b72import T7ee787org.meshtastic.core.prefs.ui.UiPrefs
Tff7b72import T7ee787org.meshtastic.core.service.IMeshService
Tff7b72import T7ee787org.meshtastic.core.service.ServiceRepository
Tff7b72import T7ee787org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
Tff7b72import T7ee787org.meshtastic.proto.LocalOnlyProtos.LocalConfig
Tff7b72import T7ee787org.meshtastic.proto.MeshProtos
Tff7b72import T7ee787org.meshtastic.proto.Portnums
Tff7b72import T7ee787timber.log.Timber
Tff7b72import T7ee787java.io.BufferedWriter
Tff7b72import T7ee787java.io.FileNotFoundException
Tff7b72import T7ee787java.io.FileWriter
Tff7b72import T7ee787java.text.SimpleDateFormat
Tff7b72import T7ee787java.util.Locale
Tff7b72import T7ee787javax.inject.Inject
Tff7b72import T7ee787kotlin.math.roundToInt
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongParameterListTa5d6ff"Tb4b4b4)
Tf0883e@HiltViewModel
Tff7b72class T56d364SettingsViewModel
Tf0883e@Inject
Tff7b72constructorTb4b4b4(
Tff7b72private Tff7b72val Te6edf3appTb4b4b4: Te6edf3ApplicationTb4b4b4,
Te6edf3radioConfigRepositoryTb4b4b4: Te6edf3RadioConfigRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3serviceRepositoryTb4b4b4: Te6edf3ServiceRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3meshLogRepositoryTb4b4b4: Te6edf3MeshLogRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3uiPrefsTb4b4b4: Te6edf3UiPrefsTb4b4b4,
Tff7b72private Tff7b72val Te6edf3uiPreferencesDataSourceTb4b4b4: Te6edf3UiPreferencesDataSourceTb4b4b4,
Tff7b72private Tff7b72val Te6edf3buildConfigProviderTb4b4b4: Te6edf3BuildConfigProviderTb4b4b4,
Tff7b72private Tff7b72val Te6edf3databaseManagerTb4b4b4: Te6edf3DatabaseManagerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3deviceHardwareRepositoryTb4b4b4: Te6edf3DeviceHardwareRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3radioPrefsTb4b4b4: Te6edf3RadioPrefsTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3ViewModelTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNodeInfoTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3MyNodeEntity?Tff7b72> Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo
Tff7b72val Te6edf3myNodeNum
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3myNodeInfoTb4b4b4.Te6edf3valueTff7b72?.Te6edf3myNodeNum
Tff7b72val Te6edf3ourNodeInfoTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3Node?Tff7b72> Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3ourNodeInfo
Tff7b72val Te6edf3isConnected Tff7b72=
Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isConnectedTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Tff7b72falseTb4b4b4)
Tff7b72val Te6edf3localConfigTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3LocalConfigTff7b72> Tff7b72=
Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3localConfigFlowTb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Te6edf3LocalConfigTb4b4b4.Te6edf3getDefaultInstanceTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3meshServiceTb4b4b4: Te6edf3IMeshService?
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3serviceRepositoryTb4b4b4.Te6edf3meshService
Tff7b72val Te6edf3provideLocationTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72=
Te6edf3myNodeInfo
Tb4b4b4.Te6edf3flatMapLatest Tb4b4b4{ Te6edf3myNodeEntity Tff7b72-Tff7b72>
T8b949e// When myNodeInfo changes, set up emissions for the "provide-location-nodeNum" pref.
Tff7b72if Tb4b4b4(Te6edf3myNodeEntity Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3flowOfTb4b4b4(Tff7b72falseTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3uiPrefsTb4b4b4.Te6edf3shouldProvideNodeLocationTb4b4b4(Te6edf3myNodeEntityTb4b4b4.Te6edf3myNodeNumTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Tff7b72falseTb4b4b4)
Tff7b72private Tff7b72val Te6edf3_excludedModulesUnlocked Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Tff7b72falseTb4b4b4)
Tff7b72val Te6edf3excludedModulesUnlockedTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3_excludedModulesUnlockedTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3appVersionName
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3buildConfigProviderTb4b4b4.Te6edf3versionName
Tff7b72val Te6edf3isDfuCapableTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72=
Te6edf3combineTb4b4b4(Te6edf3ourNodeInfoTb4b4b4, Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4) Tb4b4b4{ Te6edf3nodeTb4b4b4, Te6edf3connectionState Tff7b72-Tff7b72> Te6edf3PairTb4b4b4(Te6edf3nodeTb4b4b4, Te6edf3connectionStateTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3flatMapLatest Tb4b4b4{ Tb4b4b4(Te6edf3nodeTb4b4b4, Te6edf3connectionStateTb4b4b4) Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3node Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Tff7b72!Te6edf3connectionStateTb4b4b4.Te6edf3isConnectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3flowOfTb4b4b4(Tff7b72falseTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Check BLE address
Tff7b72val Te6edf3address Tff7b72= Te6edf3radioPrefsTb4b4b4.Te6edf3devAddr
Tff7b72if Tb4b4b4(Te6edf3address Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Tff7b72!Te6edf3addressTb4b4b4.Te6edf3startsWithTb4b4b4(Ta5d6ff"Ta5d6ffxTa5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3flowOfTb4b4b4(Tff7b72falseTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Check hardware
Tff7b72val Te6edf3hwModel Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hwModelTb4b4b4.Te6edf3number
Te6edf3flow Tb4b4b4{
Tff7b72val Te6edf3hw Tff7b72= Te6edf3deviceHardwareRepositoryTb4b4b4.Te6edf3getDeviceHardwareByModelTb4b4b4(Te6edf3hwModelTb4b4b4)Tb4b4b4.Te6edf3getOrNullTb4b4b4(Tb4b4b4)
Te6edf3emitTb4b4b4(Te6edf3hwTff7b72?.Te6edf3requiresDfu Tff7b72=Tff7b72= Tff7b72trueTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Tff7b72falseTb4b4b4)
T8b949e// Device DB cache limit (bounded by DatabaseConstants)
Tff7b72val Te6edf3dbCacheLimitTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657IntTff7b72> Tff7b72= Te6edf3databaseManagerTb4b4b4.Te6edf3cacheLimit
Tff7b72fun Td2a8ffsetDbCacheLimitTb4b4b4(Te6edf3limitTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3clamped Tff7b72= Te6edf3limitTb4b4b4.Te6edf3coerceInTb4b4b4(Te6edf3DatabaseConstantsTb4b4b4.Te6edf3MIN_CACHE_LIMITTb4b4b4, Te6edf3DatabaseConstantsTb4b4b4.Te6edf3MAX_CACHE_LIMITTb4b4b4)
Te6edf3databaseManagerTb4b4b4.Te6edf3setCacheLimitTb4b4b4(Te6edf3clampedTb4b4b4)
Tb4b4b4}
Tff7b72fun Td2a8ffsetProvideLocationTb4b4b4(Te6edf3valueTb4b4b4: Tffa657BooleanTb4b4b4) Tb4b4b4{
Te6edf3myNodeNumTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3uiPrefsTb4b4b4.Te6edf3setShouldProvideNodeLocationTb4b4b4(Tffa657itTb4b4b4, Te6edf3valueTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffsetThemeTb4b4b4(Te6edf3themeTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Te6edf3uiPreferencesDataSourceTb4b4b4.Te6edf3setThemeTb4b4b4(Te6edf3themeTb4b4b4)
Tb4b4b4}
Tff7b72fun Td2a8ffshowAppIntroTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3uiPreferencesDataSourceTb4b4b4.Te6edf3setAppIntroCompletedTb4b4b4(Tff7b72falseTb4b4b4)
Tb4b4b4}
Tff7b72fun Td2a8ffunlockExcludedModulesTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3_excludedModulesUnlockedTb4b4b4.Te6edf3update Tb4b4b4{ Tff7b72true Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Export all persisted packet data to a CSV file at the given URI.
*
* The CSV will include all packets, or only those matching the given port number if specified. Each row contains:
* date, time, sender node number, sender name, sender latitude, sender longitude, receiver latitude, receiver
* longitude, receiver elevation, received SNR, distance, hop limit, and payload.
*
* @param uri The destination URI for the CSV file.
* @param filterPortnum If provided, only packets with this port number will be exported.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffdetekt:CyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffdetekt:LongMethodTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffsaveDataCsvTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3UriTb4b4b4, Te6edf3filterPortnumTb4b4b4: Tffa657Int? Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3viewModelScopeTb4b4b4.Te6edf3launchTb4b4b4(Te6edf3DispatchersTb4b4b4.Te6edf3MainTb4b4b4) Tb4b4b4{
T8b949e// Extract distances to this device from position messages and put (node,SNR,distance)
T8b949e// in the file_uri
Tff7b72val Te6edf3myNodeNum Tff7b72= Te6edf3myNodeNum Tff7b72?: Tff7b72returnTf0883e@launch
T8b949e// Capture the current node value while we're still on main thread
Tff7b72val Te6edf3nodes Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNumTb4b4b4.Te6edf3value
T8b949e// Converts a MeshProtos.Position (nullable) to a Position, but only if it's valid, otherwise returns null.
T8b949e// The returned Position is guaranteed to be non-null and valid, or null if the input was null or invalid.
Tff7b72val Te6edf3positionToPosTb4b4b4: Tb4b4b4(Te6edf3MeshProtosTb4b4b4.Te6edf3PositionTff7b72?Tb4b4b4) Tff7b72-Tff7b72> Te6edf3Position? Tff7b72= Tb4b4b4{ Te6edf3meshPosition Tff7b72-Tff7b72>
Te6edf3meshPositionTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3PositionTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isValidTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Te6edf3writeToUriTb4b4b4(Te6edf3uriTb4b4b4) Tb4b4b4{ Te6edf3writer Tff7b72-Tff7b72>
Tff7b72val Te6edf3nodePositions Tff7b72= Te6edf3mutableMapOfTff7b72<Tffa657IntTb4b4b4, Te6edf3MeshProtosTb4b4b4.Te6edf3PositionTff7b72?Tff7b72>Tb4b4b4(Tb4b4b4)
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffMaxLineLengthTa5d6ff"Tb4b4b4)
Te6edf3writerTb4b4b4.Te6edf3appendLineTb4b4b4(
Ta5d6ff"Ta5d6ff\"Ta5d6ffdateTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftimeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fffromTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffsender nameTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffsender latTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffsender longTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffrx latTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffrx longTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffrx elevationTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffrx snrTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffdistance(m)Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffhop limitTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpayloadTa5d6ff\"Ta5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e// Packets are ordered by time, we keep most recent position of
T8b949e// our device in localNodePosition.
Tff7b72val Te6edf3dateFormat Tff7b72= Te6edf3SimpleDateFormatTb4b4b4(Ta5d6ff"Ta5d6ff\"Ta5d6ffyyyy-MM-ddTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffHH:mm:ssTa5d6ff\"Ta5d6ff"Tb4b4b4, Te6edf3LocaleTb4b4b4.Te6edf3getDefaultTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3meshLogRepositoryTb4b4b4.Te6edf3getAllLogsInReceiveOrderTb4b4b4(Tffa657IntTb4b4b4.Te6edf3MAX_VALUETb4b4b4)Tb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3packet Tff7b72-Tff7b72>
T8b949e// If we get a NodeInfo packet, use it to update our position data (if valid)
Te6edf3packetTb4b4b4.Te6edf3nodeInfoTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3nodeInfo Tff7b72-Tff7b72>
Te6edf3positionToPosTb4b4b4.Te6edf3invokeTb4b4b4(Te6edf3nodeInfoTb4b4b4.Te6edf3positionTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3nodePositionsTff7b72[Te6edf3nodeInfoTb4b4b4.Te6edf3numTff7b72] Tff7b72= Te6edf3nodeInfoTb4b4b4.Te6edf3position Tb4b4b4}
Tb4b4b4}
Te6edf3packetTb4b4b4.Te6edf3meshPacketTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3proto Tff7b72-Tff7b72>
T8b949e// If the packet contains position data then use it to update, if valid
Te6edf3packetTb4b4b4.Te6edf3positionTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3position Tff7b72-Tff7b72>
Te6edf3positionToPosTb4b4b4.Te6edf3invokeTb4b4b4(Te6edf3positionTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{
Te6edf3nodePositionsTff7b72[
Te6edf3protoTb4b4b4.Te6edf3fromTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72!Tff7b72= T79c0ff0 Tb4b4b4} Tff7b72?: Te6edf3myNodeNumTb4b4b4,
Tff7b72] Tff7b72= Te6edf3position
Tb4b4b4}
Tb4b4b4}
T8b949e// packets must have rxSNR, and optionally match the filter given as a param.
Tff7b72if Tb4b4b4(
Tb4b4b4(Te6edf3filterPortnum Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3protoTb4b4b4.Te6edf3decodedTb4b4b4.Te6edf3portnumValue Tff7b72=Tff7b72= Te6edf3filterPortnumTb4b4b4) Tff7b72&Tff7b72&
Te6edf3protoTb4b4b4.Te6edf3rxSnr Tff7b72!Tff7b72= T79c0ff0.0f
Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3rxDateTime Tff7b72= Te6edf3dateFormatTb4b4b4.Te6edf3formatTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3received_dateTb4b4b4)
Tff7b72val Te6edf3rxFrom Tff7b72= Te6edf3protoTb4b4b4.Te6edf3fromTb4b4b4.Te6edf3toUIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3senderName Tff7b72= Te6edf3nodesTff7b72[Te6edf3protoTb4b4b4.Te6edf3fromTff7b72]Tff7b72?.Te6edf3userTff7b72?.Te6edf3longName Tff7b72?: Ta5d6ff"Ta5d6ff"
T8b949e// sender lat & long
Tff7b72val Te6edf3senderPosition Tff7b72= Te6edf3nodePositionsTff7b72[Te6edf3protoTb4b4b4.Te6edf3fromTff7b72]
Tff7b72val Te6edf3senderPos Tff7b72= Te6edf3positionToPosTb4b4b4.Te6edf3invokeTb4b4b4(Te6edf3senderPositionTb4b4b4)
Tff7b72val Te6edf3senderLat Tff7b72= Te6edf3senderPosTff7b72?.Te6edf3latitude Tff7b72?: Ta5d6ff"Ta5d6ff"
Tff7b72val Te6edf3senderLong Tff7b72= Te6edf3senderPosTff7b72?.Te6edf3longitude Tff7b72?: Ta5d6ff"Ta5d6ff"
T8b949e// rx lat, long, and elevation
Tff7b72val Te6edf3rxPosition Tff7b72= Te6edf3nodePositionsTff7b72[Te6edf3myNodeNumTff7b72]
Tff7b72val Te6edf3rxPos Tff7b72= Te6edf3positionToPosTb4b4b4.Te6edf3invokeTb4b4b4(Te6edf3rxPositionTb4b4b4)
Tff7b72val Te6edf3rxLat Tff7b72= Te6edf3rxPosTff7b72?.Te6edf3latitude Tff7b72?: Ta5d6ff"Ta5d6ff"
Tff7b72val Te6edf3rxLong Tff7b72= Te6edf3rxPosTff7b72?.Te6edf3longitude Tff7b72?: Ta5d6ff"Ta5d6ff"
Tff7b72val Te6edf3rxAlt Tff7b72= Te6edf3rxPosTff7b72?.Te6edf3altitude Tff7b72?: Ta5d6ff"Ta5d6ff"
Tff7b72val Te6edf3rxSnr Tff7b72= Te6edf3protoTb4b4b4.Te6edf3rxSnr
T8b949e// Calculate the distance if both positions are valid
Tff7b72val Te6edf3dist Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3senderPos Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3rxPos Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Ta5d6ff"Ta5d6ff"
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3positionToMeterTb4b4b4(
Te6edf3PositionTb4b4b4(Te6edf3rxPositionTff7b72!!Tb4b4b4)Tb4b4b4, T8b949e// Use rxPosition but only if rxPos was
T8b949e// valid
Te6edf3PositionTb4b4b4(Te6edf3senderPositionTff7b72!!Tb4b4b4)Tb4b4b4, T8b949e// Use senderPosition but only if
T8b949e// senderPos was valid
Tb4b4b4)
Tb4b4b4.Te6edf3roundToIntTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3hopLimit Tff7b72= Te6edf3protoTb4b4b4.Te6edf3hopLimit
Tff7b72val Te6edf3payload Tff7b72=
Tff7b72when Tb4b4b4{
Te6edf3protoTb4b4b4.Te6edf3decodedTb4b4b4.Te6edf3portnumValue Tff7b72!in
Te6edf3setOfTb4b4b4(
Te6edf3PortnumsTb4b4b4.Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APP_VALUETb4b4b4,
Te6edf3PortnumsTb4b4b4.Te6edf3PortNumTb4b4b4.Te6edf3RANGE_TEST_APP_VALUETb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ff<Tffd700${Te6edf3protoTb4b4b4.Te6edf3decodedTb4b4b4.Te6edf3portnumTffd700}Ta5d6ff>Ta5d6ff"
Te6edf3protoTb4b4b4.Te6edf3hasDecodedTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Te6edf3protoTb4b4b4.Te6edf3decodedTb4b4b4.Te6edf3payloadTb4b4b4.Te6edf3toStringUtf8Tb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3replaceTb4b4b4(Ta5d6ff"Ta5d6ff\"Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff\"Ta5d6ff\"Ta5d6ff"Tb4b4b4)
Te6edf3protoTb4b4b4.Te6edf3hasEncryptedTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Ta5d6ff"Tffd700${Te6edf3protoTb4b4b4.Te6edf3encryptedTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff encrypted bytesTa5d6ff"
Tff7b72else Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ff"
Tb4b4b4}
T8b949e// date,time,from,sender name,sender lat,sender long,rx lat,rx long,rx
T8b949e// elevation,rx
T8b949e// snr,distance,hop limit,payload
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffMaxLineLengthTa5d6ff"Tb4b4b4)
Te6edf3writerTb4b4b4.Te6edf3appendLineTb4b4b4(
Ta5d6ff"Tffd700$Te6edf3rxDateTimeTa5d6ff,Ta5d6ff\"Tffd700$Te6edf3rxFromTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3senderNameTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3senderLatTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3senderLongTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3rxLatTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3rxLongTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3rxAltTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3rxSnrTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3distTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3hopLimitTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3payloadTa5d6ff\"Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72inline Tff7b72fun Td2a8ffwriteToUriTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3UriTb4b4b4, Te6edf3crossinline Te6edf3blockTb4b4b4: Te6edf3suspend Tb4b4b4(Te6edf3BufferedWriterTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tb4b4b4{
Te6edf3withContextTb4b4b4(Te6edf3DispatchersTb4b4b4.Te6edf3IOTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3appTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openFileDescriptorTb4b4b4(Te6edf3uriTb4b4b4, Ta5d6ff"Ta5d6ffwtTa5d6ff"Tb4b4b4)Tff7b72?.Te6edf3use Tb4b4b4{ Te6edf3parcelFileDescriptor Tff7b72-Tff7b72>
Te6edf3FileWriterTb4b4b4(Te6edf3parcelFileDescriptorTb4b4b4.Te6edf3fileDescriptorTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3fileWriter Tff7b72-Tff7b72>
Te6edf3BufferedWriterTb4b4b4(Te6edf3fileWriterTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3writer Tff7b72-Tff7b72> Te6edf3blockTb4b4b4.Te6edf3invokeTb4b4b4(Te6edf3writerTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3FileNotFoundExceptionTb4b4b4) Tb4b4b4{
Te6edf3TimberTb4b4b4.Te6edf3eTb4b4b4(Ta5d6ff"Ta5d6ffCan't write file error: Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.2 - Generated in 0.07s